#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
欧意（OKX）K线图显示
支持获取合约历史K线数据并在浏览器中显示
支持选择不同周期、开始时间、不同币对合约
支持获取欧意交易所成交量前十的合约
"""

import os
import json
import asyncio
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import pytz
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import webbrowser
from pathlib import Path
import dash
from dash import dcc, html, Input, Output, State, callback
import dash_bootstrap_components as dbc
import threading
import time
import aiohttp
import requests
import subprocess
from dash.long_callback import DiskcacheLongCallbackManager
from diskcache import Cache

from okx_market import OKXMarket

class OKXKlineChart:
    """
    欧意（OKX）K线图显示类 - 使用Plotly在浏览器中显示
    """
    
    def __init__(self, symbol="BTC-USDT-SWAP", interval="1m", limit=200, proxy=None, start_time=None):
        """
        初始化K线图类
        
        参数:
            symbol (str): 交易对，如 "BTC-USDT-SWAP" 表示BTC永续合约
            interval (str): K线周期，如 "1m", "5m", "15m", "1H", "4H", "1D"
            limit (int): 获取历史K线数量，默认200个周期
            proxy (str): 代理URL，如 "http://localhost:10809"
            start_time (datetime): 开始时间，默认为None表示获取最近的数据
        """
        self.symbol = symbol
        self.interval = interval
        self.limit = limit
        self.start_time = start_time
        
        # 初始化OKX行情类
        self.market = OKXMarket(proxy=proxy)
        
        # 存储K线数据的DataFrame
        self.df = pd.DataFrame()
        
        # 设置时区为UTC+8
        self.timezone = pytz.timezone('Asia/Shanghai')
        
        # BOLL指标参数
        self.boll_period = 20  # 布林带周期
        self.boll_std = 2  # 布林带标准差倍数
        
        # SAR指标参数
        self.sar_acceleration = 0.02  # SAR加速因子
        self.sar_maximum = 0.2  # SAR最大加速因子
        
        # MACD指标参数
        self.macd_fast = 12  # 快线周期
        self.macd_slow = 26  # 慢线周期
        self.macd_signal = 9  # 信号线周期
        
        # RSI指标参数
        self.rsi_period = 14  # RSI周期
        
        # 添加新的属性
        self.earliest_timestamp = None  # 记录最早的K线时间戳
        self.loading_more_data = False  # 是否正在加载更多数据的标志
        
    def process_kline_data(self, data):
        """
        处理K线数据，转换为DataFrame
        
        参数:
            data (List): OKX返回的K线数据
            
        返回:
            pd.DataFrame: 处理后的DataFrame
        """
        if not data:
            return pd.DataFrame()
            
        # 创建DataFrame
        df = pd.DataFrame(data, columns=[
            'timestamp', 'open', 'high', 'low', 'close', 
            'volume', 'volCcy', 'volCcyQuote', 'confirm'
        ])
        
        # 转换数据类型
        for col in ['open', 'high', 'low', 'close', 'volume', 'volCcy']:
            df[col] = pd.to_numeric(df[col])
            
        # 转换时间戳为datetime
        df['timestamp'] = pd.to_numeric(df['timestamp'])
        df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
        
        # 转换为UTC+8时区
        df['timestamp'] = df['timestamp'].dt.tz_localize('UTC').dt.tz_convert(self.timezone)
        
        # 设置索引
        df.set_index('timestamp', inplace=True)
        
        # 按时间排序
        df.sort_index(inplace=True)
        
        return df
        
    async def fetch_historical_data(self):
        """
        获取历史K线数据
        
        返回:
            pd.DataFrame: 历史K线数据
        """
        # 获取历史K线
        print(f"获取 {self.symbol} 的历史K线数据...")
        data = await self.market.get_candlesticks(
            self.symbol, 
            self.interval, 
            self.limit, 
            start_time=self.start_time.timestamp() * 1000 if self.start_time else None
        )
        
        if data:
            # 处理K线数据
            self.df = self.process_kline_data(data)
            print(f"获取到 {len(self.df)} 条历史K线数据")
            
            return self.df
        else:
            print("获取历史K线数据失败")
            self.df = pd.DataFrame()
            return self.df
    
    def calculate_boll(self):
        """
        计算布林带指标
        """
        if len(self.df) < self.boll_period:
            print(f"数据量不足，无法计算BOLL指标，当前数据量: {len(self.df)}, 需要: {self.boll_period}")
            return
        
        # 计算中轨（20日移动平均线）
        self.df['boll_mid'] = self.df['close'].rolling(window=self.boll_period).mean()
        
        # 计算标准差
        rolling_std = self.df['close'].rolling(window=self.boll_period).std()
        
        # 计算上轨和下轨
        self.df['boll_upper'] = self.df['boll_mid'] + (rolling_std * self.boll_std)
        self.df['boll_lower'] = self.df['boll_mid'] - (rolling_std * self.boll_std)
        
        # 使用ffill()方法填充NaN值
        self.df['boll_upper'] = self.df['boll_upper'].ffill()
        self.df['boll_mid'] = self.df['boll_mid'].ffill()
        self.df['boll_lower'] = self.df['boll_lower'].ffill()
        
        print(f"BOLL指标计算完成，有效数据点: {self.df['boll_upper'].notna().sum()}/{len(self.df)}")
        
    def calculate_sar(self):
        """
        计算抛物线转向指标(SAR)
        """
        if len(self.df) < 2:
            print("数据量不足，无法计算SAR指标")
            return None
            
        # 初始化SAR列
        self.df['sar'] = np.nan
        
        # 获取价格数据
        high = self.df['high'].values
        low = self.df['low'].values
        
        # 初始化变量
        trend = 1  # 1表示上升趋势，-1表示下降趋势
        sar = low[0]  # 初始SAR值
        ep = high[0]  # 极点
        af = self.sar_acceleration  # 加速因子
        
        # 创建一个临时数组存储SAR值
        sar_values = np.full(len(self.df), np.nan)
        
        # 计算SAR
        for i in range(1, len(self.df)):
            # 更新SAR值
            sar = sar + af * (ep - sar)
            
            # 限制SAR值
            if trend == 1:  # 上升趋势
                sar = min(sar, low[i-1], low[i-2] if i > 1 else low[i-1])
                if high[i] > ep:  # 如果创新高
                    ep = high[i]  # 更新极点
                    af = min(af + self.sar_acceleration, self.sar_maximum)  # 更新加速因子
                
                # 检查是否转向
                if low[i] < sar:
                    trend = -1  # 转为下降趋势
                    sar = ep  # SAR值设为前期极点
                    ep = low[i]  # 新极点为当前最低价
                    af = self.sar_acceleration  # 重置加速因子
            else:  # 下降趋势
                sar = max(sar, high[i-1], high[i-2] if i > 1 else high[i-1])
                if low[i] < ep:  # 如果创新低
                    ep = low[i]  # 更新极点
                    af = min(af + self.sar_acceleration, self.sar_maximum)  # 更新加速因子
                
                # 检查是否转向
                if high[i] > sar:
                    trend = 1  # 转为上升趋势
                    sar = ep  # SAR值设为前期极点
                    ep = high[i]  # 新极点为当前最高价
                    af = self.sar_acceleration  # 重置加速因子
            
            # 保存SAR值到临时数组
            sar_values[i] = sar
        
        # 一次性更新DataFrame，避免逐行赋值
        self.df['sar'] = sar_values
        
        print(f"SAR指标计算完成，有效数据点: {self.df['sar'].notna().sum()}/{len(self.df)}")
        return self.df['sar']
    
    def calculate_macd(self):
        """
        计算MACD指标
        """
        if len(self.df) < self.macd_slow + self.macd_signal:
            print(f"数据量不足，无法计算MACD指标，当前数据量: {len(self.df)}, 需要: {self.macd_slow + self.macd_signal}")
            return
        
        # 计算快线EMA
        self.df['macd_fast_ema'] = self.df['close'].ewm(span=self.macd_fast, adjust=False).mean()
        
        # 计算慢线EMA
        self.df['macd_slow_ema'] = self.df['close'].ewm(span=self.macd_slow, adjust=False).mean()
        
        # 计算MACD线 = 快线EMA - 慢线EMA
        self.df['macd'] = self.df['macd_fast_ema'] - self.df['macd_slow_ema']
        
        # 计算信号线 = MACD的EMA
        self.df['macd_signal'] = self.df['macd'].ewm(span=self.macd_signal, adjust=False).mean()
        
        # 计算柱状图 = MACD线 - 信号线
        self.df['macd_hist'] = self.df['macd'] - self.df['macd_signal']
        
        print(f"MACD指标计算完成，有效数据点: {self.df['macd'].notna().sum()}/{len(self.df)}")
    
    def calculate_rsi(self):
        """
        计算RSI指标
        """
        if len(self.df) < self.rsi_period + 1:
            print(f"数据量不足，无法计算RSI指标，当前数据量: {len(self.df)}, 需要: {self.rsi_period + 1}")
            return
        
        # 计算价格变化
        delta = self.df['close'].diff()
        
        # 分离上涨和下跌
        gain = delta.where(delta > 0, 0)
        loss = -delta.where(delta < 0, 0)
        
        # 计算平均上涨和下跌
        avg_gain = gain.rolling(window=self.rsi_period).mean()
        avg_loss = loss.rolling(window=self.rsi_period).mean()
        
        # 计算相对强度
        rs = avg_gain / avg_loss
        
        # 计算RSI
        self.df['rsi'] = 100 - (100 / (1 + rs))
        
        print(f"RSI指标计算完成，有效数据点: {self.df['rsi'].notna().sum()}/{len(self.df)}")
    
    def calculate_fractals(self):
        """
        计算Fractals指标
        """
        if len(self.df) < 5:
            print("数据量不足，无法计算Fractals指标")
            return
            
        # 初始化Fractals列
        self.df.loc[:, 'fractal_up'] = np.nan
        self.df.loc[:, 'fractal_down'] = np.nan
        
        # 计算上升分形
        for i in range(2, len(self.df) - 2):
            if (self.df['high'].iloc[i] > self.df['high'].iloc[i-1] and 
                self.df['high'].iloc[i] > self.df['high'].iloc[i-2] and
                self.df['high'].iloc[i] > self.df['high'].iloc[i+1] and
                self.df['high'].iloc[i] > self.df['high'].iloc[i+2]):
                self.df.loc[self.df.index[i], 'fractal_up'] = self.df['high'].iloc[i]
                
        # 计算下降分形
        for i in range(2, len(self.df) - 2):
            if (self.df['low'].iloc[i] < self.df['low'].iloc[i-1] and
                self.df['low'].iloc[i] < self.df['low'].iloc[i-2] and
                self.df['low'].iloc[i] < self.df['low'].iloc[i+1] and
                self.df['low'].iloc[i] < self.df['low'].iloc[i+2]):
                self.df.loc[self.df.index[i], 'fractal_down'] = self.df['low'].iloc[i]
                
        print(f"Fractals指标计算完成，上升分形: {self.df['fractal_up'].notna().sum()}个，下降分形: {self.df['fractal_down'].notna().sum()}个")
    
    async def on_message(self, message):
        """
        处理WebSocket消息
        
        参数:
            message (str): WebSocket消息
        """
        try:
            data = json.loads(message) if isinstance(message, str) else message
            
            if 'data' in data:
                # 处理K线数据
                new_data = data['data']
                if new_data:
                    # 转换为DataFrame
                    new_df = self.process_kline_data(new_data)
                    
                    if not new_df.empty:
                        # 合并数据
                        self.df = pd.concat([new_df, self.df]).drop_duplicates()
                        self.df.sort_index(inplace=True)
                        
                        # 更新最早时间戳
                        self.earliest_timestamp = self.df.index.min()
                        
                        print(f"更新K线数据成功，当前数据量: {len(self.df)}")
                        
                        # 重新计算技术指标
                        self.calculate_boll()
                        self.calculate_sar()
                        self.calculate_macd()
                        self.calculate_rsi()
                        self.calculate_fractals()
                    
        except Exception as e:
            print(f"处理WebSocket消息时出错: {e}")
            import traceback
            traceback.print_exc()
    
    async def load_more_history(self, end_time=None):
        """
        加载更多历史K线数据
        
        参数:
            end_time (datetime): 结束时间，默认为最早的K线时间
        """
        if self.loading_more_data:
            print("正在加载数据中，请稍后再试...")
            return
            
        try:
            self.loading_more_data = True
            
            # 如果没有指定结束时间，使用当前数据中最早的时间
            if end_time is None and self.earliest_timestamp is not None:
                end_time = self.earliest_timestamp
            
            print(f"加载更多历史数据，结束时间: {end_time}")
            
            # 获取历史K线
            data = await self.market.get_candlesticks(
                self.symbol,
                self.interval,
                self.limit,
                end_time=end_time.timestamp() * 1000 if end_time else None
            )
            
            if data:
                # 处理K线数据
                new_df = self.process_kline_data(data)
                
                if not new_df.empty:
                    # 合并数据
                    self.df = pd.concat([new_df, self.df]).drop_duplicates()
                    self.df.sort_index(inplace=True)
                    
                    # 更新最早时间戳
                    self.earliest_timestamp = self.df.index.min()
                    
                    print(f"加载历史数据成功，当前数据量: {len(self.df)}")
                    
                    # 重新计算技术指标
                    self.calculate_boll()
                    self.calculate_sar()
                    self.calculate_macd()
                    self.calculate_rsi()
                    self.calculate_fractals()
                    
                    return True
            
            return False
            
        except Exception as e:
            print(f"加载更多历史数据时出错: {e}")
            import traceback
            traceback.print_exc()
            return False
        finally:
            self.loading_more_data = False
            
    def create_plotly_chart(self):
        """
        创建Plotly K线图并在浏览器中显示
        """
        if self.df is None or len(self.df) == 0:
            print("没有K线数据，无法创建图表")
            return None
        
        print(f"创建K线图...共有{len(self.df)}条K线数据")
        
        # 创建子图，主图显示K线，副图显示MACD、RSI和成交量
        fig = make_subplots(
            rows=4, 
            cols=1, 
            shared_xaxes=True,
            vertical_spacing=0.02,
            row_heights=[0.5, 0.15, 0.15, 0.2],
            subplot_titles=None
        )
        
        # 设置白色主题
        fig.update_layout(
            template="plotly",
            paper_bgcolor='white',
            plot_bgcolor='white',
            font=dict(
                family="Arial",
                size=12,
                color='#333333'
            ),
            showlegend=True,
            legend=dict(
                bgcolor='rgba(255,255,255,0.9)',
                bordercolor='rgba(0,0,0,0.1)',
                borderwidth=1,
                font=dict(size=10, color='#333333'),
                orientation="h",
                yanchor="bottom",
                y=1.02,
                xanchor="right",
                x=1
            ),
            margin=dict(l=10, r=10, t=10, b=10),
            dragmode='pan',  # 默认平移模式
            modebar=dict(
                bgcolor='rgba(255,255,255,0.9)',
                color='#333333',
                activecolor='#26A69A'
            ),
            xaxis_rangeslider_visible=False,
            height=800,
        )
        
        # 添加K线图
        fig.add_trace(
            go.Candlestick(
                x=self.df.index,
                open=self.df['open'],
                high=self.df['high'],
                low=self.df['low'],
                close=self.df['close'],
                name="K线",
                increasing=dict(line=dict(color='#26A69A', width=1), fillcolor='#26A69A'),  # 绿色
                decreasing=dict(line=dict(color='#EF5350', width=1), fillcolor='#EF5350')   # 红色
            ),
            row=1, col=1
        )
        
        # 添加最新价格线
        latest_price = self.df['close'].iloc[-1]
        price_color = '#26A69A' if self.df['close'].iloc[-1] >= self.df['open'].iloc[-1] else '#EF5350'
        
        # 添加水平价格线
        fig.add_hline(
            y=latest_price,
            line=dict(
                color=price_color,
                width=1,
                dash='dash'
            ),
            opacity=0.8,
            row=1, col=1
        )
        
        # 添加最新价格标签
        fig.add_annotation(
            x=self.df.index[-1],
            y=latest_price,
            text=f"当前价格: {latest_price:.2f}",
            showarrow=True,
            arrowhead=2,
            arrowsize=1,
            arrowwidth=2,
            arrowcolor=price_color,
            font=dict(size=12, color=price_color),
            align='left',
            xanchor='right',
            yanchor='bottom',
            bgcolor='rgba(255,255,255,0.8)',
            bordercolor=price_color,
            borderwidth=1,
            borderpad=4,
            row=1, col=1
        )
        
        # 添加布林带
        if 'boll_upper' in self.df.columns and not self.df['boll_upper'].isna().all():
            fig.add_trace(
                go.Scatter(
                    x=self.df.index,
                    y=self.df['boll_upper'],
                    name="BOLL上轨",
                    line=dict(color='rgba(38, 166, 154, 0.5)', width=1),
                    mode='lines'
                ),
                row=1, col=1
            )
            
            fig.add_trace(
                go.Scatter(
                    x=self.df.index,
                    y=self.df['boll_mid'],
                    name="BOLL中轨",
                    line=dict(color='rgba(128, 128, 128, 0.3)', width=1, dash='dash'),
                    mode='lines'
                ),
                row=1, col=1
            )
            
            fig.add_trace(
                go.Scatter(
                    x=self.df.index,
                    y=self.df['boll_lower'],
                    name="BOLL下轨",
                    line=dict(color='rgba(239, 83, 80, 0.5)', width=1),
                    mode='lines',
                    fill='tonexty',
                    fillcolor='rgba(38, 166, 154, 0.03)'
                ),
                row=1, col=1
            )
        
        # 添加SAR指标
        if 'sar' in self.df.columns and not self.df['sar'].isna().all():
            fig.add_trace(
                go.Scatter(
                    x=self.df.index,
                    y=self.df['sar'],
                    name="SAR",
                    mode='markers',
                    marker=dict(
                        color='#FFD700',
                        size=3,
                        symbol='circle'
                    )
                ),
                row=1, col=1
            )
        
        # 添加Fractals指标
        if 'fractal_up' in self.df.columns:
            fig.add_trace(
                go.Scatter(
                    x=self.df.index,
                    y=self.df['fractal_up'],
                    name="上升分形",
                    mode='markers',
                    marker=dict(
                        color='#26A69A',
                        size=6,
                        symbol='triangle-up'
                    ),
                    hoverinfo='none'
                ),
                row=1, col=1
            )
            
        if 'fractal_down' in self.df.columns:
            fig.add_trace(
                go.Scatter(
                    x=self.df.index,
                    y=self.df['fractal_down'],
                    name="下降分形",
                    mode='markers',
                    marker=dict(
                        color='#EF5350',
                        size=6,
                        symbol='triangle-down'
                    ),
                    hoverinfo='none'
                ),
                row=1, col=1
            )
        
        # 添加MACD指标
        if 'macd' in self.df.columns and not self.df['macd'].isna().all():
            colors = ['#26A69A' if val >= 0 else '#EF5350' for val in self.df['macd_hist']]
            
            fig.add_trace(
                go.Bar(
                    x=self.df.index,
                    y=self.df['macd_hist'],
                    name="MACD柱状图",
                    marker_color=colors,
                    opacity=0.8
                ),
                row=2, col=1
            )
            
            fig.add_trace(
                go.Scatter(
                    x=self.df.index,
                    y=self.df['macd'],
                    name="MACD线",
                    line=dict(color='#2196F3', width=1),
                    mode='lines'
                ),
                row=2, col=1
            )
            
            fig.add_trace(
                go.Scatter(
                    x=self.df.index,
                    y=self.df['macd_signal'],
                    name="信号线",
                    line=dict(color='#FF9800', width=1),
                    mode='lines'
                ),
                row=2, col=1
            )
        
        # 添加RSI指标
        if 'rsi' in self.df.columns and not self.df['rsi'].isna().all():
            fig.add_trace(
                go.Scatter(
                    x=self.df.index,
                    y=self.df['rsi'],
                    name="RSI",
                    line=dict(color='#E91E63', width=1),
                    mode='lines'
                ),
                row=3, col=1
            )
            
            # 添加RSI参考线
            for level, color in [(30, '#26A69A'), (50, '#888888'), (70, '#EF5350')]:
                fig.add_trace(
                    go.Scatter(
                        x=self.df.index,
                        y=[level] * len(self.df),
                        name=f"RSI {level}",
                        line=dict(color=color, width=1, dash='dot'),
                        mode='lines',
                        showlegend=False
                    ),
                    row=3, col=1
                )
        
        # 添加成交量
        colors = ['#26A69A' if row['close'] >= row['open'] else '#EF5350' for _, row in self.df.iterrows()]
        
        fig.add_trace(
            go.Bar(
                x=self.df.index,
                y=self.df['volume'],
                name="成交量",
                marker_color=colors,
                opacity=0.8
            ),
            row=4, col=1
        )
        
        # 更新X轴样式
        fig.update_xaxes(
            gridcolor='rgba(0,0,0,0.05)',
            zerolinecolor='rgba(0,0,0,0.1)',
            showgrid=True,
            zeroline=True,
            type="date",
            tickformat='%H:%M\n%m-%d',  # 显示时间和日期
            tickfont=dict(size=10)
        )
        
        # 更新Y轴样式
        fig.update_yaxes(
            gridcolor='rgba(0,0,0,0.05)',
            zerolinecolor='rgba(0,0,0,0.1)',
            showgrid=True,
            zeroline=True,
            tickfont=dict(size=10),
            tickformat='.2f',  # 保留两位小数
            side='right'  # Y轴刻度显示在右侧
        )
        
        # 添加指标标签
        fig.add_annotation(
            text="MACD(12,26,9)",
            xref="paper",
            yref="paper",
            x=0,
            y=0.35,
            showarrow=False,
            font=dict(size=10, color='#888888')
        )
        
        fig.add_annotation(
            text="RSI(14)",
            xref="paper",
            yref="paper",
            x=0,
            y=0.20,
            showarrow=False,
            font=dict(size=10, color='#888888')
        )
        
        fig.add_annotation(
            text="VOL",
            xref="paper",
            yref="paper",
            x=0,
            y=0.05,
            showarrow=False,
            font=dict(size=10, color='#888888')
        )
        
        return fig

    async def subscribe_ticker(self, symbol):
        """
        获取最新的ticker数据
        
        参数:
            symbol (str): 交易对名称
        返回:
            dict: ticker数据
        """
        try:
            # 获取ticker数据
            ticker_data = await self.market.get_ticker(symbol)
            if ticker_data and 'data' in ticker_data and len(ticker_data['data']) > 0:
                ticker = ticker_data['data'][0]
                
                # 计算24小时涨跌幅
                price_change = float(ticker['last']) - float(ticker['open24h'])
                price_change_pct = (price_change / float(ticker['open24h'])) * 100
                
                # 格式化成交量
                volume_24h = float(ticker['vol24h'])
                volume_ccy_24h = float(ticker['volCcy24h'])
                
                # 打印详细的行情信息
                print("\n" + "="*80)
                print(f"【{symbol}】最新行情信息 - {pd.Timestamp.now(tz=self.timezone).strftime('%Y-%m-%d %H:%M:%S')}")
                print("="*80)
                print(f"最新价格: {float(ticker['last']):,.2f} USDT")
                print(f"24h开盘价: {float(ticker['open24h']):,.2f} USDT")
                print(f"24h最高价: {float(ticker['high24h']):,.2f} USDT")
                print(f"24h最低价: {float(ticker['low24h']):,.2f} USDT")
                print(f"24h涨跌幅: {price_change:+,.2f} USDT ({price_change_pct:+.2f}%)")
                print(f"24h成交量: {volume_24h:,.2f} 张")
                print(f"24h成交额: {volume_ccy_24h:,.2f} USDT")
                print(f"买一价: {float(ticker['bidPx']):,.2f} USDT")
                print(f"卖一价: {float(ticker['askPx']):,.2f} USDT")
                print(f"买一量: {float(ticker['bidSz']):,.2f}")
                print(f"卖一量: {float(ticker['askSz']):,.2f}")
                print("="*80)
                
                # 更新最新K线数据
                if len(self.df) > 0:
                    latest_ts = pd.to_datetime(int(ticker['ts']), unit='ms')
                    latest_ts = latest_ts.tz_localize('UTC').tz_convert(self.timezone)
                    
                    # 获取最新K线的时间
                    last_candle_time = self.df.index[-1]
                    
                    # 如果ticker时间在当前K线周期内，更新最后一根K线
                    if latest_ts.floor(self.interval) == last_candle_time:
                        # 更新前的值
                        old_close = self.df.loc[last_candle_time, 'close']
                        old_high = self.df.loc[last_candle_time, 'high']
                        old_low = self.df.loc[last_candle_time, 'low']
                        old_volume = self.df.loc[last_candle_time, 'volume']
                        
                        # 更新K线数据
                        self.df.loc[last_candle_time, 'close'] = float(ticker['last'])
                        self.df.loc[last_candle_time, 'high'] = max(self.df.loc[last_candle_time, 'high'], float(ticker['last']))
                        self.df.loc[last_candle_time, 'low'] = min(self.df.loc[last_candle_time, 'low'], float(ticker['last']))
                        self.df.loc[last_candle_time, 'volume'] = float(ticker['vol24h'])
                        
                        # 打印K线更新信息
                        print("\n" + "-"*80)
                        print(f"K线更新信息 - {last_candle_time.strftime('%Y-%m-%d %H:%M:%S')}")
                        print("-"*80)
                        print(f"收盘价: {old_close:.2f} -> {float(ticker['last']):.2f}")
                        print(f"最高价: {old_high:.2f} -> {self.df.loc[last_candle_time, 'high']:.2f}")
                        print(f"最低价: {old_low:.2f} -> {self.df.loc[last_candle_time, 'low']:.2f}")
                        print(f"成交量: {old_volume:.2f} -> {float(ticker['vol24h']):.2f}")
                        print("-"*80 + "\n")
                        return True
                    else:
                        # 如果是新的K线周期，获取完整的K线数据
                        print("\n新的K线周期开始，获取完整K线数据...\n")
                        await self.fetch_historical_data()
                        return True
                        
            return False
        except Exception as e:
            print(f"获取ticker数据失败: {e}")
            return False

def get_top_volume_symbols(proxy=None, limit=10):
    """
    获取欧意交易所成交量前十的合约
    
    参数:
        proxy (str): 代理URL
        limit (int): 获取数量，默认10个
        
    返回:
        list: 成交量前十的合约列表
    """
    try:
        # 设置代理
        proxies = None
        if proxy:
            proxies = {
                'http': proxy,
                'https': proxy
            }
        
        print(f"正在获取成交量前{limit}的合约...")
        print(f"使用代理: {proxy}" if proxy else "不使用代理")
        
        # 尝试使用备用API端点
        urls = [
            "https://www.okx.com/api/v5/market/tickers?instType=SWAP",
            "https://www.okx.com/api/v5/public/instruments?instType=SWAP",
            "https://www.okx.com/api/v5/market/ticker?instType=SWAP"
        ]
        
        response = None
        data = None
        
        # 尝试所有URL直到成功
        for url in urls:
            try:
                print(f"尝试请求URL: {url}")
                
                # 添加请求头，模拟浏览器请求
                headers = {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
                    'Accept': 'application/json'
                }
                
                # 发送请求，添加超时设置
                response = requests.get(url, proxies=proxies, headers=headers, timeout=10)
                
                print(f"API响应状态码: {response.status_code}")
                
                if response.status_code == 200:
                    # 尝试解析JSON
                    data = response.json()
                    print("成功解析API响应JSON")
                    
                    if 'data' in data and len(data['data']) > 0:
                        print(f"成功获取数据，包含 {len(data['data'])} 条记录")
                        break
                    else:
                        print("API返回数据为空或格式不正确，尝试下一个URL")
                else:
                    print(f"请求失败，尝试下一个URL")
            except Exception as e:
                print(f"请求 {url} 出错: {e}")
                continue
        
        # 如果所有URL都失败
        if not data or 'data' not in data:
            print("所有API请求都失败了")
            
            # 使用硬编码的热门合约作为备用
            fallback_symbols = [
                "BTC-USDT-SWAP", "ETH-USDT-SWAP", "SOL-USDT-SWAP", 
                "XRP-USDT-SWAP", "DOGE-USDT-SWAP", "ADA-USDT-SWAP",
                "AVAX-USDT-SWAP", "DOT-USDT-SWAP", "LINK-USDT-SWAP", 
                "MATIC-USDT-SWAP"
            ]
            print(f"使用备用合约列表: {fallback_symbols}")
            return fallback_symbols
            
        # 提取合约信息
        contracts = []
        
        # 根据不同的API响应格式处理数据
        for item in data['data']:
            try:
                if 'instId' in item:
                    # 第一种API格式
                    if 'volCcy24h' in item and 'last' in item:
                        contracts.append({
                            'symbol': item['instId'],
                            'volume': float(item.get('volCcy24h', 0)),
                            'price': float(item.get('last', 0))
                        })
                    # 第二种API格式
                    elif 'vol24h' in item:
                        contracts.append({
                            'symbol': item['instId'],
                            'volume': float(item.get('vol24h', 0)),
                            'price': 0
                        })
                    # 其他可能的格式
                    else:
                        contracts.append({
                            'symbol': item['instId'],
                            'volume': 0,
                            'price': 0
                        })
            except (KeyError, ValueError) as e:
                print(f"处理合约数据时出错: {e}, 数据: {item}")
                continue
                
        print(f"成功提取 {len(contracts)} 个合约信息")
            
        if not contracts:
            print("没有提取到有效的合约信息")
            # 使用硬编码的热门合约作为备用
            fallback_symbols = [
                "BTC-USDT-SWAP", "ETH-USDT-SWAP", "SOL-USDT-SWAP", 
                "XRP-USDT-SWAP", "DOGE-USDT-SWAP", "ADA-USDT-SWAP",
                "AVAX-USDT-SWAP", "DOT-USDT-SWAP", "LINK-USDT-SWAP", 
                "MATIC-USDT-SWAP"
            ]
            print(f"使用备用合约列表: {fallback_symbols}")
            return fallback_symbols
            
        # 按成交量排序
        contracts.sort(key=lambda x: x['volume'], reverse=True)
        
        # 返回前N个合约
        top_contracts = contracts[:limit]
        
        # 提取合约名称
        symbols = [contract['symbol'] for contract in top_contracts]
        
        print(f"获取到成交量前{limit}的合约: {symbols}")
        return symbols
        
    except requests.exceptions.RequestException as e:
        print(f"网络请求异常: {e}")
        # 使用硬编码的热门合约作为备用
        fallback_symbols = [
            "BTC-USDT-SWAP", "ETH-USDT-SWAP", "SOL-USDT-SWAP", 
            "XRP-USDT-SWAP", "DOGE-USDT-SWAP", "ADA-USDT-SWAP",
            "AVAX-USDT-SWAP", "DOT-USDT-SWAP", "LINK-USDT-SWAP", 
            "MATIC-USDT-SWAP"
        ]
        print(f"使用备用合约列表: {fallback_symbols}")
        return fallback_symbols
    except Exception as e:
        print(f"获取成交量前十合约失败: {e}")
        import traceback
        traceback.print_exc()  # 打印完整的错误堆栈
        # 使用硬编码的热门合约作为备用
        fallback_symbols = [
            "BTC-USDT-SWAP", "ETH-USDT-SWAP", "SOL-USDT-SWAP", 
            "XRP-USDT-SWAP", "DOGE-USDT-SWAP", "ADA-USDT-SWAP",
            "AVAX-USDT-SWAP", "DOT-USDT-SWAP", "LINK-USDT-SWAP", 
            "MATIC-USDT-SWAP"
        ]
        print(f"使用备用合约列表: {fallback_symbols}")
        return fallback_symbols

# 添加获取所有合约的函数
def get_all_contracts(proxy=None):
    """
    获取欧意支持的所有合约
    
    参数:
        proxy (str): 代理URL
        
    返回:
        list: 合约列表
    """
    try:
        # 设置代理
        proxies = None
        if proxy:
            proxies = {
                'http': proxy,
                'https': proxy
            }
        
        print("正在获取所有合约信息...")
        print(f"使用代理: {proxy}" if proxy else "不使用代理")
        
        # 获取所有合约类型的数据
        inst_types = ['SWAP', 'FUTURES', 'SPOT']
        all_contracts = []
        
        for inst_type in inst_types:
            try:
                url = f"https://www.okx.com/api/v5/public/instruments?instType={inst_type}"
                
                # 添加请求头
                headers = {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
                    'Accept': 'application/json'
                }
                
                # 发送请求
                response = requests.get(url, proxies=proxies, headers=headers, timeout=10)
                
                if response.status_code == 200:
                    data = response.json()
                    if 'data' in data:
                        contracts = data['data']
                        # 添加合约类型标签
                        for contract in contracts:
                            contract['instType'] = inst_type
                        all_contracts.extend(contracts)
                        print(f"获取到 {len(contracts)} 个 {inst_type} 合约")
                
            except Exception as e:
                print(f"获取 {inst_type} 合约列表失败: {e}")
                continue
        
        # 处理合约数据
        formatted_contracts = []
        for contract in all_contracts:
            try:
                inst_id = contract.get('instId', '')
                inst_type = contract.get('instType', '')
                base_ccy = contract.get('baseCcy', '')
                quote_ccy = contract.get('quoteCcy', '')
                
                # 创建合约选项
                label = f"{inst_id} ({inst_type})"
                if base_ccy and quote_ccy:
                    label += f" - {base_ccy}/{quote_ccy}"
                
                formatted_contracts.append({
                    'label': label,
                    'value': inst_id
                })
            except Exception as e:
                print(f"处理合约数据出错: {e}")
                continue
        
        print(f"总共获取到 {len(formatted_contracts)} 个合约")
        return formatted_contracts
        
    except Exception as e:
        print(f"获取合约列表失败: {e}")
        import traceback
        traceback.print_exc()
        return []

# 创建Dash应用
app = dash.Dash(
    __name__, 
    external_stylesheets=[dbc.themes.BOOTSTRAP],
    long_callback_manager=DiskcacheLongCallbackManager(
        Cache("./cache")
    )
)
app.title = "欧意K线图"

# 启用回调函数的重复输出和异步支持
app.config.suppress_callback_exceptions = True

# 定义时间周期选项
interval_options = [
    {'label': '1分钟', 'value': '1m'},
    {'label': '3分钟', 'value': '3m'},
    {'label': '5分钟', 'value': '5m'},
    {'label': '15分钟', 'value': '15m'},
    {'label': '30分钟', 'value': '30m'},
    {'label': '1小时', 'value': '1H'},
    {'label': '2小时', 'value': '2H'},
    {'label': '4小时', 'value': '4H'},
    {'label': '6小时', 'value': '6H'},
    {'label': '12小时', 'value': '12H'},
    {'label': '1天', 'value': '1D'},
    {'label': '1周', 'value': '1W'},
    {'label': '1月', 'value': '1M'}
]

# 添加自动刷新开关
app.layout = dbc.Container([
    # 添加定时器组件
    dcc.Interval(
        id='auto-refresh',
        interval=3000,  # 3秒 = 3000毫秒
        n_intervals=0,
        disabled=False,  # 确保定时器默认启用
        max_intervals=-1  # 设置为-1表示无限循环
    ),
    
    dbc.Row([
        dbc.Col([
            html.H1("欧意K线图", className="text-center my-4"),
        ], width=10),
        dbc.Col([
            # 添加自动刷新开关到标题行
            dbc.Switch(
                id='auto-refresh-switch',
                label='自动刷新',
                value=True,  # 设置默认值为True
                className="mt-4"
            ),
        ], width=2),
        html.Hr(),
    ]),
    
    dbc.Row([
        dbc.Col([
            dbc.Card([
                dbc.CardHeader("筛选条件"),
                dbc.CardBody([
                    dbc.Row([
                        dbc.Col([
                            html.Label("搜索合约:"),
                            dcc.Dropdown(
                                id='symbol-search',
                                placeholder="输入合约名称搜索",
                                value="BTC-USDT-SWAP",
                                clearable=False,
                                searchable=True,  # 启用搜索功能
                                options=[],  # 初始为空，由回调函数更新
                                style={'width': '100%'}  # 设置宽度
                            ),
                        ], width=12),
                    ]),
                    
                    dbc.Row([
                        dbc.Col([
                            html.Label("选择周期:"),
                            dcc.Dropdown(
                                id='interval-dropdown',
                                options=interval_options,
                                value='1m',
                                clearable=False
                            ),
                        ], width=6),
                        dbc.Col([
                            html.Label("数据条数:"),
                            dcc.Slider(
                                id='limit-slider',
                                min=100,
                                max=1000,
                                step=100,
                                value=200,
                                marks={i: str(i) for i in range(100, 1001, 100)},
                            ),
                        ], width=6),
                    ], className="mt-3"),
                    
                    dbc.Row([
                        dbc.Col([
                            html.Label("开始时间:"),
                            dcc.DatePickerSingle(
                                id='date-picker',
                                date=datetime.now().date() - timedelta(days=1),
                                display_format='YYYY-MM-DD'
                            ),
                        ], width=6),
                        dbc.Col([
                            dbc.Button(
                                "生成K线图", 
                                id="generate-button", 
                                color="success", 
                                className="mt-4"
                            ),
                        ], width=6),
                    ], className="mt-3"),
                ]),
            ]),
        ], width=12),
    ]),
    
    dbc.Row([
        dbc.Col([
            dbc.Spinner(
                dcc.Graph(id="kline-chart", style={"height": "800px"}),
                color="primary",
                type="grow",
            ),
        ], width=12, className="mt-4"),
    ]),
    
    dbc.Row([
        dbc.Col([
            html.Div(id="status-message", className="text-center mt-3"),
        ], width=12),
    ]),
    
    # 存储组件
    dcc.Store(id='contracts-store'),
    dcc.Store(id='chart-store', storage_type='memory'),  # 添加新的Store组件存储图表状态
    
], fluid=True)

# 添加回调函数，初始化合约列表
@app.callback(
    [Output('contracts-store', 'data'),
     Output('symbol-search', 'options')],
    [Input('symbol-search', 'search_value')],
    [State('contracts-store', 'data')]
)
def update_contract_options(search_value, stored_contracts):
    ctx = dash.callback_context
    
    if not stored_contracts:
        # 首次加载，获取交易量前十的合约
        print("首次加载，获取交易量前十的合约...")
        top_symbols = get_top_volume_symbols(proxy="http://localhost:10809", limit=10)
        # 获取所有合约
        all_contracts = get_all_contracts(proxy="http://localhost:10809")
        
        if not all_contracts:
            # 如果获取失败，至少显示交易量前十的合约
            all_contracts = [{'label': s, 'value': s} for s in top_symbols]
        
        # 将交易量前十的合约标记为热门
        for contract in all_contracts:
            if contract['value'] in top_symbols:
                contract['label'] = "🔥 " + contract['label']  # 添加火焰图标表示热门
        
        # 将交易量前十的合约排在前面
        top_contracts = [c for c in all_contracts if c['value'] in top_symbols]
        other_contracts = [c for c in all_contracts if c['value'] not in top_symbols]
        
        # 添加分隔符
        if top_contracts and other_contracts:
            top_contracts.append({'label': '──────────', 'value': '', 'disabled': True})
        
        # 合并列表
        all_contracts = top_contracts + other_contracts
        print(f"初始化完成，共加载 {len(all_contracts)} 个合约")
        return all_contracts, all_contracts
    
    if not search_value:
        # 如果搜索值为空，返回所有合约
        return stored_contracts, stored_contracts
    
    # 过滤合约
    filtered_contracts = [
        contract for contract in stored_contracts
        if search_value.upper() in contract['label'].upper() and not contract.get('disabled', False)
    ]
    
    print(f"搜索 '{search_value}'，找到 {len(filtered_contracts)} 个匹配的合约")
    return stored_contracts, filtered_contracts

# 修改生成K线图的回调函数
@app.long_callback(
    [Output('kline-chart', 'figure'),
     Output('status-message', 'children')],
    [Input('generate-button', 'n_clicks')],
    [State('symbol-search', 'value'),
     State('interval-dropdown', 'value'),
     State('date-picker', 'date'),
     State('limit-slider', 'value')],
    prevent_initial_call=True,
    running=[
        (Output('generate-button', 'disabled'), True, False),  # 运行时禁用按钮
        (Output('status-message', 'children', allow_duplicate=True), '正在生成K线图...', None),  # 显示加载消息
    ]
)
def generate_kline_chart(n_clicks, symbol, interval, start_date, limit):
    """
    生成K线图的回调函数
    """
    if not n_clicks:  # 如果不是按钮点击触发，不更新
        raise dash.exceptions.PreventUpdate
        
    if not symbol or not interval:
        return go.Figure(), "请选择交易对和时间周期"
        
    try:
        print(f"正在生成K线图... 交易对: {symbol}, 周期: {interval}, 数据量: {limit}")
        
        # 转换开始时间
        start_time = None
        if start_date:
            try:
                start_time = pd.to_datetime(start_date)
                print(f"设置开始时间: {start_time}")
            except Exception as e:
                print(f"转换开始时间出错: {e}")
        
        # 创建K线图对象
        chart = OKXKlineChart(
            symbol=symbol,
            interval=interval,
            limit=limit,
            proxy="http://localhost:10809",
            start_time=start_time
        )
        
        # 获取历史数据
        asyncio.run(chart.fetch_historical_data())
        
        # 检查是否成功获取数据
        if len(chart.df) == 0:
            return go.Figure(), f"无法获取 {symbol} 的历史K线数据"
        
        print(f"成功获取 {len(chart.df)} 条K线数据")
        
        # 计算技术指标
        chart.calculate_boll()
        chart.calculate_sar()
        chart.calculate_macd()
        chart.calculate_rsi()
        chart.calculate_fractals()
        
        # 创建Plotly图表
        fig = chart.create_plotly_chart()
        if fig is None:
            return go.Figure(), "创建图表失败"
            
        # 获取最新价格信息
        latest_price = chart.df['close'].iloc[-1]
        price_change = chart.df['close'].iloc[-1] - chart.df['open'].iloc[-1]
        price_change_pct = (price_change / chart.df['open'].iloc[-1]) * 100
        
        # 生成状态信息
        status = f"【{symbol}】{interval}周期 K线图生成成功\n"
        status += f"最新价格: {latest_price:,.2f} USDT "
        status += f"涨跌: {price_change:+,.2f} ({price_change_pct:+.2f}%)\n"
        status += f"数据时间范围: {chart.df.index[0].strftime('%Y-%m-%d %H:%M:%S')} 至 {chart.df.index[-1].strftime('%Y-%m-%d %H:%M:%S')}\n"
        status += f"当前数据量: {len(chart.df)} 条"
        
        print(f"K线图生成成功: {status}")
        
        return fig, status
        
    except Exception as e:
        print(f"生成K线图时出错: {e}")
        import traceback
        traceback.print_exc()
        return go.Figure(), f"生成K线图出错: {str(e)}"

# 修改回调函数，处理图表布局更新
@app.callback(
    [Output('kline-chart', 'figure', allow_duplicate=True),
     Output('status-message', 'children', allow_duplicate=True)],
    [Input('kline-chart', 'relayoutData')],
    [State('symbol-search', 'value'),
     State('interval-dropdown', 'value'),
     State('kline-chart', 'figure')],
    prevent_initial_call='initial_duplicate'
)
async def update_chart_on_pan(relayout_data, symbol, interval, current_figure):
    """
    当用户平移图表到边缘时，自动加载更多历史数据
    """
    if not relayout_data or 'xaxis.range[0]' not in relayout_data:
        raise dash.exceptions.PreventUpdate
        
    try:
        # 获取当前显示的时间范围
        x_range_start = pd.to_datetime(relayout_data['xaxis.range[0]'])
        x_range_end = pd.to_datetime(relayout_data['xaxis.range[1]'])
        
        # 计算显示范围的时间跨度
        time_span = x_range_end - x_range_start
        
        # 如果视图已经接近左边缘（最早的数据），加载更多历史数据
        chart = OKXKlineChart(
            symbol=symbol,
            interval=interval,
            limit=200,
            proxy="http://localhost:10809",
            start_time=x_range_start - time_span  # 加载更多历史数据
        )
        
        # 加载更多历史数据
        success = await chart.load_more_history(end_time=x_range_start)
        
        if success and len(chart.df) > 0:
            # 创建新的图表
            new_figure = chart.create_plotly_chart()
            
            # 保持当前的缩放级别
            new_figure['layout']['xaxis']['range'] = [x_range_start, x_range_end]
            
            return new_figure, f"已加载更多历史数据，当前共有 {len(chart.df)} 条K线"
            
    except Exception as e:
        print(f"更新图表时出错: {e}")
        import traceback
        traceback.print_exc()
        
    raise dash.exceptions.PreventUpdate

def auto_git_commit():
    """
    自动执行git add和commit操作
    """
    try:
        # 获取当前时间作为commit信息
        commit_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        commit_message = f"自动提交: K线图更新 - {commit_time}"
        
        # 执行git命令
        subprocess.run(['git', 'add', '.'], check=True)
        subprocess.run(['git', 'commit', '-m', commit_message], check=True)
        print(f"Git提交成功: {commit_message}")
        
    except subprocess.CalledProcessError as e:
        print(f"Git操作失败: {e}")
    except Exception as e:
        print(f"执行git命令时出错: {e}")

# 修改自动刷新回调函数
@app.callback(
    [Output('chart-store', 'data', allow_duplicate=True),
     Output('status-message', 'children', allow_duplicate=True)],
    [Input('auto-refresh', 'n_intervals'),
     Input('auto-refresh-switch', 'value')],
    [State('symbol-search', 'value'),
     State('interval-dropdown', 'value'),
     State('chart-store', 'data')],
    prevent_initial_call=True
)
def update_chart_auto(n_intervals, auto_refresh_enabled, symbol, interval, current_data):
    """
    每3秒自动更新K线数据
    """
    if not auto_refresh_enabled:
        raise dash.exceptions.PreventUpdate
        
    if not symbol or not interval:
        raise dash.exceptions.PreventUpdate
        
    try:
        print(f"正在自动更新K线图... 间隔次数: {n_intervals}")
        
        # 创建K线图对象
        chart = OKXKlineChart(
            symbol=symbol,
            interval=interval,
            limit=200,
            proxy="http://localhost:10809"
        )
        
        # 获取历史数据
        asyncio.run(chart.fetch_historical_data())
        
        if len(chart.df) == 0:
            raise dash.exceptions.PreventUpdate
            
        # 计算技术指标
        chart.calculate_boll()
        chart.calculate_sar()
        chart.calculate_macd()
        chart.calculate_rsi()
        chart.calculate_fractals()
        
        # 创建新的图表
        new_figure = chart.create_plotly_chart()
        if new_figure is None:
            raise dash.exceptions.PreventUpdate
            
        # 如果当前图表存在，保持当前的缩放级别
        if current_data and 'layout' in current_data:
            if 'xaxis.range' in current_data['layout']:
                new_figure['layout']['xaxis']['range'] = current_data['layout']['xaxis']['range']
            elif 'xaxis.range[0]' in current_data['layout']:
                new_figure['layout']['xaxis']['range'] = [
                    current_data['layout']['xaxis.range[0]'],
                    current_data['layout']['xaxis.range[1]']
                ]
                
            # 保持Y轴范围
            if 'yaxis.range' in current_data['layout']:
                new_figure['layout']['yaxis']['range'] = current_data['layout']['yaxis']['range']
            elif 'yaxis.range[0]' in current_data['layout']:
                new_figure['layout']['yaxis']['range'] = [
                    current_data['layout']['yaxis.range[0]'],
                    current_data['layout']['yaxis.range[1]']
                ]
        
        # 添加更新时间戳
        update_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        latest_price = chart.df['close'].iloc[-1]
        price_change = chart.df['close'].iloc[-1] - chart.df['open'].iloc[-1]
        price_change_pct = (price_change / chart.df['open'].iloc[-1]) * 100
        
        status = f"【{symbol}】{interval}周期 K线图更新成功\n"
        status += f"最新价格: {latest_price:,.2f} USDT "
        status += f"涨跌: {price_change:+,.2f} ({price_change_pct:+.2f}%)\n"
        status += f"更新时间: {update_time}\n"
        status += f"当前数据量: {len(chart.df)} 条"
        
        print(f"K线图更新成功: {status}")
        
        return new_figure, status
            
    except Exception as e:
        print(f"自动更新K线图时出错: {e}")
        import traceback
        traceback.print_exc()
        
    raise dash.exceptions.PreventUpdate

# 修改初始加载数据的回调
@app.callback(
    [Output('chart-store', 'data'),
     Output('status-message', 'children')],
    [Input('symbol-search', 'value'),
     Input('interval-dropdown', 'value')],
    prevent_initial_call=True
)
def update_chart_data(symbol, interval):
    """
    当选择新的交易对或时间周期时，更新图表数据
    """
    if not symbol or not interval:
        return None, "请选择交易对和时间周期"
        
    try:
        print(f"更新 {symbol} {interval} 的K线数据...")
        
        # 创建K线图对象
        chart = OKXKlineChart(
            symbol=symbol,
            interval=interval,
            limit=200,
            proxy="http://localhost:10809"
        )
        
        # 获取历史数据
        asyncio.run(chart.fetch_historical_data())
        
        if len(chart.df) == 0:
            return None, f"无法获取 {symbol} 的历史K线数据"
            
        # 计算技术指标
        chart.calculate_boll()
        chart.calculate_sar()
        chart.calculate_macd()
        chart.calculate_rsi()
        chart.calculate_fractals()
        
        # 创建新的图表
        fig = chart.create_plotly_chart()
        if fig is None:
            return None, "创建图表失败"
            
        status = f"已加载 {symbol} {interval} K线图，共 {len(chart.df)} 条数据"
        print(status)
        
        return fig, status
            
    except Exception as e:
        print(f"更新K线图数据时出错: {e}")
        import traceback
        traceback.print_exc()
        return None, f"更新K线图数据出错: {str(e)}"

# 添加图表显示回调
@app.callback(
    Output('kline-chart', 'figure'),
    [Input('chart-store', 'data')],
    prevent_initial_call=True
)
def display_chart(chart_data):
    """
    从Store中获取图表数据并显示
    """
    if not chart_data:
        return go.Figure()
    return chart_data

# 修改自动刷新开关回调
@app.callback(
    Output('auto-refresh', 'disabled'),
    [Input('auto-refresh-switch', 'value')]
)
def toggle_auto_refresh(enabled):
    """
    控制自动刷新开关
    """
    return not enabled

async def main():
    """
    主函数
    """
    try:
        # 确保git仓库已初始化
        try:
            subprocess.run(['git', 'rev-parse', '--is-inside-work-tree'], check=True, capture_output=True)
        except subprocess.CalledProcessError:
            print("初始化git仓库...")
            subprocess.run(['git', 'init'], check=True)
            subprocess.run(['git', 'add', '.'], check=True)
            subprocess.run(['git', 'commit', '-m', "初始化提交"], check=True)
            print("git仓库初始化完成")
            
        print("启动Dash应用服务器，请访问 http://127.0.0.1:8050/")
        # 自动打开浏览器
        webbrowser.open('http://127.0.0.1:8050/')
        app.run_server(debug=True, use_reloader=False)
        
    except Exception as e:
        print(f"启动服务器时出错: {e}")
        import traceback
        traceback.print_exc()

if __name__ == "__main__":
    try:
        # 使用asyncio运行Dash应用
        import nest_asyncio
        nest_asyncio.apply()
        asyncio.run(main())
    except KeyboardInterrupt:
        print("程序已停止")
    except Exception as e:
        print(f"发生错误: {e}")
        import traceback
        traceback.print_exc() 